/*
    Copyright (C) 2011  Jiabo <jiabo2011@gmail.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

static const uint8_t parity_table[256] = {
	CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
	0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
	0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
	CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
	0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
	CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
	CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
	0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
	0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
	CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
	CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
	0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
	CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
	0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
	0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
	CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
	0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
	CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
	CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
	0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
	CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
	0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
	0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
	CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
	CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
	0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
	0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
	CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
	0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
	CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
	CC_P, 0, 0, CC_P, 0, CC_P, CC_P, 0,
	0, CC_P, CC_P, 0, CC_P, 0, 0, CC_P,
};

static inline void gen_arm_flags(void)
{
	extern unsigned char gen_arm_flags_start, gen_arm_flags_end;
	uint32_t len = &gen_arm_flags_end - &gen_arm_flags_start;
	memcpy((char *)gen_code_ptr, &gen_arm_flags_start, len);
	gen_code_ptr += len / 4;
}

#define extsb(out, in)\
	do {\
	arm_mov_reg_lsl_imm(out, in, 24);\
	arm_mov_reg_asr_imm(out, out, 24);\
	} while (0)
#define extsw(out, in)\
	do {\
	arm_mov_reg_lsl_imm(out, in, 16);\
	arm_mov_reg_asr_imm(out, out, 16);\
	} while (0)
#define extub(out, in)\
	do {\
		arm_and_rd_rn_imm(out, in, 0xff, 0);\
	} while (0)
#define extuw(out, in)\
	do {\
	arm_mov_reg_lsl_imm(out, in, 16);\
	arm_mov_reg_lsr_imm(out, out, 16);\
	} while (0)

static inline void exts(int ot, uint8_t reg_out, uint8_t reg_in)
{
	switch (ot) {
	case OT_BYTE:
		extsb(reg_out, reg_in);
		break;
	case OT_WORD:
		extsw(reg_out, reg_in);
		break;
	case OT_LONG:
		if (reg_out != reg_in)
			arm_mov_reg(reg_out, reg_in);
		break;
	}
}

/*
	T0 saves the original eflags
*/
static inline void gen_x86_flags_optimize(DisasContext *s1)
{
	int ot = s1->cc_op & 3;
	int opc = s1->cc_op & (~3);

//	printf("%s: opc %s\n", __func__, cc_op_str[s1->cc_op]);

	if ((opc == CC_OP_LOGICB) ||
		(opc == CC_OP_INCB) || (opc == CC_OP_DECB)) {
		exts(ot, T2, OP1);
	} else {
		exts(ot, OP1, OP1);
		exts(ot, OP2, OP2);
	}

	arm_mov_reg_imm(R0, 0, 0);
	switch (opc) {
	case CC_OP_LOGICB:
		arm_cmp_reg_imm(T2, 0, 0);
		goto gen_f;
	case CC_OP_ADDB:
		arm_adds_reg(T2, OP1, OP2);
		arm_orr_reg_imm_cond(COND_CS, R0, CC_C, 0);
		break;
	case CC_OP_INCB:
		arm_tst_reg_imm(T0, CC_C, 0);
		arm_orr_reg_imm_cond(COND_NE, R0, CC_C, 0);
		arm_adds_reg_imm(T2, 1, 0);
		break;
	case CC_OP_DECB:
		arm_tst_reg_imm(T0, CC_C, 0);
		arm_orr_reg_imm_cond(COND_NE, R0, CC_C, 0);
		arm_subs_reg_imm(T2, 1, 0);
		break;
	case CC_OP_CMPB:
	case CC_OP_SUBB:
		arm_subs_reg(T2, OP1, OP2);
		arm_orr_reg_imm_cond(COND_CC, R0, CC_C, 0);
		break;
	case CC_OP_SHRB:
		// not completed
		break;
	default:
		printf("error gen flags %x\n", opc);
		exit(0);
	}

	arm_eor_reg(T1, OP1, OP2);
	arm_eor_reg(T1, T2, T1);
	arm_and_reg_imm(T1, CC_A, 0);
	arm_orr_reg(R0, R0, T1);
gen_f:
	arm_orr_reg_imm_cond(COND_VS, R0, (CC_O >> 8), 12);
	arm_orr_reg_imm_cond(COND_EQ, R0, CC_Z, 0);
	arm_orr_reg_imm_cond(COND_MI, R0, CC_S, 0);
}

static inline void gen_x86_flags(DisasContext *s1)
{
//	printf("%s: can %soptimize\n", __func__, s1->opc_valid ? "" : "not ");
	if (s1->opc_valid) {
		if (s1->cc_op != CC_OP_EFLAGS) {
			load_x86env(OT_LONG, T0, eflags);
			gen_x86_flags_optimize(s1);
			arm_bic_reg_imm(T0, 0xd5, 0);
			arm_orr_reg(T0, T0, R0);
		
			gen_mov_reg_imm(T1, (uint32_t)parity_table);
			arm_and_rd_rn_imm(T2, T2, 0xff, 0);
			arm_add_reg(T1, T1, T2);
			arm_ldr_data(OT_BYTE, T1, T2);
			arm_orr_reg(T0, T0, T2);
		
			store_x86env(OT_LONG, T0, eflags);
		}
	} else {
		arm_cmp_reg_imm(OPC, CC_OP_EFLAGS, 0);
		gen_b_label_cond(COND_EQ, 0);
	
		load_x86env(OT_LONG, T0, eflags);
		gen_arm_flags();
		arm_bic_reg_imm(T0, 0xd5, 0);
		arm_orr_reg(T0, T0, R0);
	
		gen_mov_reg_imm(T1, (uint32_t)parity_table);
		arm_and_rd_rn_imm(T2, T2, 0xff, 0);
		arm_add_reg(T1, T1, T2);
		arm_ldr_data(OT_BYTE, T1, T2);
		arm_orr_reg(T0, T0, T2);
	
		store_x86env(OT_LONG, T0, eflags);
	
		mark_b_label(0);
	}
	s1->cc_op = CC_OP_EFLAGS;
	s1->opc_valid = 1;
}

static inline void gen_cmp(DisasContext *s1, int cond)
{
	int ot = s1->cc_op & 3;
	int opc = s1->cc_op & (~3);

//	printf("%s: opc %s\n", __func__, cc_op_str[s1->cc_op]);

	// inc/dec ??
	if (opc == CC_OP_LOGICB) {
		exts(ot, OP1, OP1);
	} else {
		exts(ot, OP1, OP1);
		exts(ot, OP2, OP2);
	}

	switch (opc) {
	case CC_OP_LOGICB:
		arm_cmp_reg_imm(OP1, 0, 0);
		break;
	case CC_OP_SHRB:
		if (cond == 4)
			arm_movs_reg_lsr_reg(T1, OP1, OP2);
		else {
			printf("%s: unsupported cond %x\n", __func__, cond);
			exit(0);
		}
		break;
	case CC_OP_CMPB:
	case CC_OP_SUBB:
		arm_cmp_reg(OP1, OP2);
		break;
	default:
		printf("%s: unsupported option\n", __func__);
		exit(0);
	}
}

static inline void gen_jcc(DisasContext *s1, int cond, uint32_t offset, uint32_t next_eip)
{
	enum arm_cond_code_e arm_cond;

	if (s1->opc_valid == 0) {
		printf("opc depends on other tb!\n");
		gen_x86_flags(s1);
		switch (cond) {
		case 2: /* JB/JC/JNAE(CF=1) */
			arm_tst_reg_imm(R0, CC_C, 0);
			gen_mov_reg_imm_cond(COND_EQ, SPC, next_eip);
			gen_mov_reg_imm_cond(COND_NE, SPC, offset);
			break;
		case 3: /* JAE/JNC/JNB(CF=0) */
			arm_tst_reg_imm(R0, CC_C, 0);
			gen_mov_reg_imm_cond(COND_NE, SPC, next_eip);
			gen_mov_reg_imm_cond(COND_EQ, SPC, offset);
			break;
		case 6: /* JBE/JNA(CF=1 or ZF=1) */
			arm_tst_reg_imm(R0, CC_C | CC_Z, 0);
			gen_mov_reg_imm_cond(COND_EQ, SPC, next_eip);
			gen_mov_reg_imm_cond(COND_NE, SPC, offset);
			break;
		default:
			printf("%s: unsupported option %d\n", __func__, cond);
			exit(0);
		}
		return;
	}

	gen_mov_reg_imm(SPC, next_eip);
	switch (cond) {
	case 0: /* JO(OF=1) */
		gen_cmp(s1, cond);
		arm_cond = COND_VS;
		debug_stop = 1;
		break;
	case 1: /* JNO(OF=0) */
		gen_cmp(s1, cond);
		arm_cond = COND_VC;
		debug_stop = 1;
		break;
	case 2: /* JB/JC/JNAE(CF=1) */
		gen_cmp(s1, cond);
		arm_cond = COND_CC;
		break;
	case 3: /* JAE/JNC/JNB(CF=0) */
		gen_cmp(s1, cond);
		arm_cond = COND_CS;
		break;
	case 4: /* JE/JZ(ZF=1) */
		gen_cmp(s1, cond);
		arm_cond = COND_EQ;
		break;
	case 5: /* JNE/JNZ(ZF=0) */
		gen_cmp(s1, cond);
		arm_cond = COND_NE;
		break;
	case 6: /* JBE/JNA(CF=1 or ZF=1) */
		gen_cmp(s1, cond);
		arm_cond = COND_LS;
		break;
	case 7: /* JA/JNBE(CF=0 and ZF=0) */
		gen_cmp(s1, cond);
		arm_cond = COND_HI;
		break;
	case 8: /* JS(SF=1) */
	case 9: /* JNS(SF=0) */
	case 10: /* JP/JPE(PF=1) */
	case 11: /* JO(PF=0) */
		printf("unsupported Jcc %x\n", cond);
		exit(0);
	case 12: /* JL(SF<>OF) */
		gen_cmp(s1, cond);
		arm_cond = COND_LT;
		break;
	case 13: /* JNL(SF=OF) */
		gen_cmp(s1, cond);
		arm_cond = COND_GE;
		break;
	case 14: /* JLE/JNG(ZF=1 or SF<>OF) */
		printf("unsupported Jcc %x\n", cond);
		exit(0);
	case 15: /* JG/JNLE(ZF=0 and SF=OF) */
		gen_cmp(s1, cond);
		arm_cond = COND_GT;
		break;
	}
	gen_mov_reg_imm_cond(arm_cond, SPC, offset);
}
